package com.young.rpc.http;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.young.domain.ResultCode;
import okhttp3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @description：DefaultScheduleHttpClient
 * @author: yangyahui01
 * @date: 11/25/24 4:50 PM
 */
public class DefaultScheduleHttpClient implements ScheduleHttpClient {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultScheduleHttpClient.class);

    private OkHttpClient httpClient;

    private final int retryTimes = 1;

    private volatile boolean started;

    private volatile boolean closed;

    private static final String POST_MEDIA_TYPE = "application/json; charset=utf-8";

    private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.setName("ScheduleHttpClient - asyncSender");
        return thread;
    });


    /**
     * initMethod
     */
    @Override
    public void start() {
        if (isStarted()) {
            return;
        }

        httpClient = build();

        started = true;

        Runtime.getRuntime().addShutdownHook(new Thread(() -> this.close()));
        LOGGER.info("young-schedule-client http客户端已开启 - ScheduleHttpClient");
    }

    private OkHttpClient build() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(1000, TimeUnit.MILLISECONDS);

        //不能设置最大连接数，默认连接是可重用的。
        ConnectionPool connectionPool = new ConnectionPool(3, 12, TimeUnit.SECONDS);
        builder.connectionPool(connectionPool);
        builder.callTimeout(Duration.ofSeconds(12));//单次请求最高3S

        builder.readTimeout(6000, TimeUnit.MILLISECONDS);
        builder.retryOnConnectionFailure(true);
        builder.writeTimeout(1500, TimeUnit.MILLISECONDS);
        return builder.build();
    }

    /**
     * destroyMethod
     */
    @Override
    public void close() {
        if (isClosed()) {
            return;
        }
        httpClient.dispatcher().executorService().shutdown();
        httpClient.dispatcher().cancelAll();
        httpClient.connectionPool().evictAll();
        if (!executor.isShutdown()) {
            executor.shutdown();
        }
        closed = true;
        LOGGER.info("young-schedule-client的http客户端已关闭 - DefaultScheduleHttpClient");
    }

    /**
     * 事件发送,同步
     *
     * @param requestBody
     * @return
     */
    @Override
    public boolean send(RequestBody requestBody) {

        Future<Boolean> future = asyncSend(requestBody);

        try {
            return future.get();
        } catch (Exception e) {
            LOGGER.error("报警信息发送失败", e);
        }

        return false;
    }

    /**
     * 异步发送
     *
     * @param requestBody
     * @return
     */
    @Override
    public Future<Boolean> asyncSend(RequestBody requestBody) {
        String path = requestBody.getUrl();
        return executor.submit(callable(path, requestBody.getParameter()));
    }


    private Callable<Boolean> callable(String path, Map<String, Object> parameters) {
        return () -> DefaultScheduleHttpClient.this.remoteAction(path, parameters);
    }

    private Boolean remoteAction(String path, Map<String, Object> parameters) {
        Request.Builder builder = new Request.Builder();
        okhttp3.RequestBody body = okhttp3.RequestBody.create(MediaType.parse(POST_MEDIA_TYPE), "");
        Request request = builder.url(url(path, parameters)).post(body).build();
        int i = 0;
        //只重试网络失败情况
        while (i < retryTimes) {
            Response response = null;
            try {
                response = httpClient.newCall(request).execute();
                if (!response.isSuccessful()) {
                    LOGGER.error("job callback 信息发送失败，code:{}，message:{}", response.code(), response.message());
                    continue;
                }
                JsonElement result = new JsonParser().parse(response.body().string());
                //响应结果体不符合预期，直接返回
                if (result == null || result.isJsonNull() || !result.isJsonObject()) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("job callback 信息发送失败，响应体解析失败！");
                    }
                    return false;
                }
                JsonObject object = result.getAsJsonObject();
                if (!object.has("code")) {
                    return false;
                }

                String code = object.get("code").getAsString();
                if (!ResultCode.SUCCESS.code.equals(code)) {
                    String message = object.has("message") ? object.get("message").getAsString() : "";
                    LOGGER.warn("job callback 信息发送失败，code:{},message:{}", code, message);
                    return false;
                }
                return true;

            } catch (Exception e) {
                LOGGER.error("job callback 信息发送失败，网络错误，当前第{}次请求，总共需要重试{}次", i, retryTimes, e);
            } finally {
                i++;
                if (response != null) {
                    response.close();
                }
            }
        }
        return false;

    }

    private String url(String uri, Map<String, Object> parameters) {
        if (parameters == null || parameters.isEmpty()) {
            return uri;
        }
        StringBuilder sb = new StringBuilder(uri);
        if (!uri.contains("?")) {
            sb.append("?1=1");
        }
        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
            sb.append("&").append(entry.getKey())
                    .append("=")
                    .append(encode(entry.getValue() == null ? null : entry.getValue().toString()));
        }
        return sb.toString();
    }

    private String encode(String source) {
        if (source == null || source.isEmpty()) {
            return "";
        }
        String result = "";
        try {
            result = URLEncoder.encode(source, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            return "";
        }
        return result;
    }

    @Override
    public boolean isStarted() {
        return started;
    }

    @Override
    public boolean isClosed() {
        return closed;
    }
}
